using System;
using System.Collections.Generic;
using System.Reflection;

namespace Fadd.Data.Mappings
{
    /// <summary>
    /// Tables and Classes are identical.
    /// </summary>
    /// <remarks>
    /// Since they are identical, we create a mapping which simply returns the
    /// same values for the mapping.
    /// </remarks>
    public class MirrorLoader : ILoader
    {
        private readonly List<Assembly> _assemblies = new List<Assembly>();
        private readonly string _generatorType;

        /// <summary>
        /// Initializes a new instance of the <see cref="MirrorLoader"/> class.
        /// </summary>
        /// <param name="assembly">Assembly that will be scanned after mappings.</param>
        /// <param name="generatorType">"sequence" or "autoincrement"</param>
        /// <remarks>
        /// Specifying "sequence" means that the system will look for generators/sequences called "[tablename]_id_seq".
        /// </remarks>
        public MirrorLoader(Assembly assembly, string generatorType)
        {
            Check.Require(assembly, "assembly");
            _assemblies.Add(assembly);
            _generatorType = generatorType;
        }

        /// <summary>
        /// Gets the mapping.
        /// </summary>
        /// <param name="className">Name of the class to get mapping for.</param>
        /// <returns><see cref="IMapping"/> if found; otherwise null.</returns>
        public IMapping GetMapping(string className)
        {
            Type type = FindClass(className);
            if (type == null)
                return null;


            Mapping mapping = new Mapping(type, type.Name == "User" ? "[User]" : type.Name);
            foreach (PropertyInfo property in type.GetProperties())
            {
                if (!property.CanRead || !property.CanWrite)
                    continue;
                if (property.Name == "Id")
                    mapping.Add(property.Name, property.Name, true, _generatorType);
                else
                    mapping.Add(property.Name, property.Name);
            }
            return mapping;
        }

        /// <exception cref="InvalidOperationException">Multiple classes found.</exception>
        private Type FindClass(string className)
        {
            List<Type> foundTypes = new List<Type>();
            foreach (Assembly assembly in _assemblies)
            {
                foreach (Type type in assembly.GetTypes())
                {
                    if (type.Name == className)
                        foundTypes.Add(type);
                }
            }

            if (foundTypes.Count == 0)
                return null;
			if (foundTypes.Count > 1)
				throw new InvalidOperationException("Multiple classes found.");
        	

        	Type foundType = foundTypes[0];
        	PropertyInfo id = foundType.GetProperty("Id");
			if (id == null || !id.CanRead || !id.CanWrite)
				return null;
        	return foundType;
        }

        /// <summary>
        /// Add an assembly in which we can look for mappings.
        /// </summary>
        /// <param name="assembly">An assembly</param>
        public void Add(Assembly assembly)
        {
            if (!_assemblies.Contains(assembly))
                _assemblies.Add(assembly);
        }
    }
}
